Webpack项目中如何解决浏览器兼容性问题 您所在的位置:网站首页 vue 兼容性 安卓 Webpack项目中如何解决浏览器兼容性问题

Webpack项目中如何解决浏览器兼容性问题

2023-11-14 22:18| 来源: 网络整理| 查看: 265

Webpack本身的兼容性

既然是基于Webpack,那么首先要看Webpack的Browser端代码的兼容性。

Webpack 支持所有符合 ES5 标准 的浏览器。webpack 的 import() 和 require.ensure() 依赖 Promise。如果你想要支持旧版本浏览器,在使用这些表达式之前,还需要 提前加载 polyfill。

即在支持ES5标准的浏览器(几乎所有现代浏览器)上,Webpack自身的代码是可以正常运行的。但如果使用到了import()与require.ensure,则需要我们额外引入Promise(ES6语法)的polyfill,才能在一些低版本浏览器上运行Webpack。

构建目标 & browserlist

接下来就是处理我们项目代码的兼容性了。但在这之前,我们需要了解一个概念,Webpack的构建目标 target。由于JavaScript既可以编写浏览器代码也可以编写服务器端代码,所以 webpack 提供了多种部署 target。target配置的默认值是browserslist,如果项目是在浏览器上运行,那么保持这个默认配置就可以了。

重点来了,browserslist是什么呢?它是一个浏览器兼容性配置,并且它被多个我们常用的JS工具使用,比如Babel,Eslint。具体怎么用呢?Browserlist有一些方便理解的查询语法,如:

> 5% // 全球用户量大于5%的浏览器 last 2 versions // 各浏览器的最近2个版本 not dead // "dead"指超过24个月没有官方支持或更新的浏览器

通过这些查询语句就能找到符合要求的厂商与版本的浏览器,从而Babel等工具可以利用它来做针对性的语法转换或其他工作。

browserslist可以在项目的package.json或.browserlistrc文件中配置,如create-react-app在package.json中的配置为:

"browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } JavaScript代码兼容性 语法转换

在不配置任何Loader的情况下,Webpack可以将不同模块的JS文件打包在一起。但是不会进行任何兼容性处理,比如const var1 = 1,在打包后仍然是const,而不会是ES5语法的var。

要进行语法转换,我们需要引入合适的loader。这里使用专门用来处理JS语法转换的一个工具:Babel。Babel是一个JavaScript编译器,它主要用来将ES6+的高级语法转换为兼容性更好的语法,从而支持一些老版本的浏览器。

在Webpack中,我们可以添加对应的babel-loader。安装npm install -D babel-loader @babel/core @babel/preset-env。配置如下:

module: { rules: [ { test: /.m?js$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } } ] }

先看看我们所预期的转换结果:const,数组解构等ES6语法都被转换为了ES5的语法!

// 转换前 const var1 = 1; const [a, b] = [1, 2]; // 转换后 var var1 = 1; var a = 1, b = 2;

具体是如何做的呢?注意到我们前面我们安装了三个库:babel-loader,@babel/core和@babel/preset-env。

babel-loader:babel的Webpack loader。 @babel/core:babel编译器的核心库。包含源码转AST,AST转源码等核心工具函数。 @babel/preset-env:一个babel preset。它可以根据浏览器兼容性配置来做针对性的语法转换。

@babel/preset-env是将Babel转换与浏览器兼容性衔接起来的重要一环。@babel/preset-env利用了browserslist等工具提供的数据维护了一份JS特性到支持浏览器/平台的映射。内容如下所示:

{ "transform-class-static-block": { "chrome": "94", "opera": "80", "edge": "94", "firefox": "93", "node": "16.11", "deno": "1.14", "samsung": "17", "electron": "15.0" }, "transform-private-property-in-object": { "chrome": "91", "opera": "77", "edge": "91", "firefox": "90", "safari": "15", "node": "16.9", "deno": "1.9", "ios": "15", "samsung": "16", "electron": "13.0" } }

同时,它还维护了一份这些JS特性到对应的Babel转换插件以及core-js polyfills的映射,从而实现在各个平台上应用合适的Babel插件。这样用户只需要按browserslist的语法指定需要的浏览器版本,@babel/preset-env就能将源码编译为平台支持的语法了。

Polyfill

在处理JS兼容性问题上,除了语法转换,还有一件重要事情是polyfill。polyfill的目的是注入一些API,比如Object.assign,以在浏览器本身不支持的情况下调用。注入的示例如下,即在原有对象或全局对象上做一套方法实现。

// from mdn if (!String.prototype.includes) { String.prototype.includes = function(search, start) { 'use strict'; if (search instanceof RegExp) { throw TypeError('first argument must not be a RegExp'); } if (start === undefined) { start = 0; } return this.indexOf(search, start) !== -1; }; }

语法转换与Polyfill是各司其职的。像const到var这种语法层面的就只能通过转译来做,像String.prototype.includes这种API方法就需要polyfill。另外,polyfill的一个好处是,如果浏览器中存在原生方法时将优先使用,这样性能与安全性更好。

@babel/preset-env可以根据浏览器兼容性配置一次性做好语法转换与Polyfill。但为了使用Polyfill,我们需要配置useBuiltIns选项,同时安装core-js,使用npm install core-js@3即可(注意这里包含的是运行时代码,所以不放到devDependency中)。

在@babel/preset-env7.4.0版本中,推荐直接使用core-js而不是@babel/polyfill。并且需要在core-js选项中指定core-js的版本。

配置方法如下:

presets: [ [ "@babel/preset-env", { useBuiltIns: "entry", corejs: "3", }, ], ]

useBuiltIns有三个选项:

entry

将把源代码中import "core-js",import "core-js/stable"指令转换为拆分的import。如:

require("core-js/modules/es.symbol.js"); require("core-js/modules/es.symbol.description.js"); require("core-js/modules/es.symbol.async-iterator.js"); require("core-js/modules/es.symbol.has-instance.js"); // ...

这些拆分的引入是根据指定的浏览器版本来确定的,从而既做到了兼容性,又减小了体积。但要注意,使用它的前提是你需要在代码中先引入core-js。

usage

相比于entry,它不会引入全部polyfill,而只会引入代码中用到了的API的polyfill,这样代码体积就更小了。另外,它不需要你在源代码中手动引入core-js。比如你只用到了Object.assign,则编译后的代码中只会增加一行:

require("core-js/modules/es.object.assign.js"); false

不引入任何polyfill。

问题又来了,实际项目中到底使用哪个选项呢?貌似看起来usage是最佳选择。但实际上,create-react-app脚手架与umi框架中默认都是用的entry。原因可能是usage还不太完善,在一些特殊情况下,它无法识别出需要polyfill的API,有兴趣大家可以babel的issue里看看。

CSS代码兼容性

CSS也像JS一样,由于不同浏览器的支持程度不一致,所以也存在兼容性问题。

postcss是一个可以转换CSS语法的JS插件。在Webpack中使用postcss-loader示例如下:

module: { rules: [ { test: /.css$/i, use: ["style-loader", "css-loader", "postcss-loader"], } ] }

postcss与Babel相似,它本身只包含一些核心API,但不处理具体的转换工作,所以需要添加一些postcss的plugin,如autoprefixer,postcss-preset-env。这些插件可以在项目下的postcss.config.js文件中配置:

const postcssPresetEnv = require("postcss-preset-env"); module.exports = { plugins: [require("autoprefixer"), postcssPresetEnv(/* pluginOptions */)], };

比如autoprefixer可以根据提供的目标浏览器来添加对应的CSS前缀,以解决一些兼容性问题。如下面示例所示:

/* 转换前 */ .example { display: grid; transition: all .5s; user-select: none; background: linear-gradient(to bottom, white, black); } /* * 转换后 * Prefixed by https://autoprefixer.github.io * PostCSS: v8.4.14, * Autoprefixer: v10.4.7 * Browsers: last 4 version */ .example { display: -ms-grid; display: grid; -webkit-transition: all .5s; -o-transition: all .5s; transition: all .5s; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; background: -webkit-gradient(linear, left top, left bottom, from(white), to(black)); background: -o-linear-gradient(top, white, black); background: linear-gradient(to bottom, white, black); }

在create-react-app与一些流行的前端框架中都用到了postcss,有兴趣的读者可以继续探索啦~

参考 why-do-i-need-babel-polyfill-at-all-using-babel 关于Babel的详细解读


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有